/**
 *
 */
package com.wedroid.r2d2.message.handler;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.wedroid.entity.Constant;
import com.wedroid.entity.command.Command;
import com.wedroid.entity.command.GroupMentionLog;
import com.wedroid.entity.command.TemplateType;
import com.wedroid.entity.contact.Contacter;
import com.wedroid.entity.contact.GroupMember;
import com.wedroid.entity.message.MessageType;
import com.wedroid.r2d2.cache.PersistentAccessor;
import com.wedroid.r2d2.contact.Contact;
import com.wedroid.r2d2.contact.ContactManager;
import com.wedroid.r2d2.message.RipeMessage;
import com.wedroid.r2d2.message.command.CommandOperator;
import com.wedroid.r2d2.service.CommandService;
import com.wedroid.r2d2.service.ContactService;
import com.wedroid.r2d2.service.GroupMentionLogService;

/**
 * @author lute
 */
@Component("commandMessageHandler")
public class CommandMessageHandler extends AsyncMessageHandler {

    private static final Pattern MENTION_PATTERN = Pattern.compile("@(.+?)\\u2005([^@\\u2005]*)");

    @Autowired
    private PersistentAccessor accessor;

    @Autowired
    private ContactManager contactManager;

    @Autowired
    private ContactService contactService;

    @Autowired
    private CommandService commandService;

    @Autowired
    private GroupMentionLogService groupMentionLogService;

    @Autowired
    private List<CommandOperator> operators;

    private Map<TemplateType, CommandOperator> operatorCache = new HashMap<>(8);

    /* (non-Javadoc)
     * @see com.wedroid.r2d2.message.handler.AsyncMessageHandler#handleMessageInternal(com.wedroid.r2d2.message.RipeMessage)
     */
    @Override
    protected void handleMessageInternal(RipeMessage ripeMessage) {
        Optional<List<String>> mentionValues = extractMentionValues(ripeMessage);
        if (mentionValues.isPresent()) {
            List<String> values = mentionValues.get();
            logger.trace("Get mention values {}", values);

            Contact sender = ripeMessage.getSender();
            Contact droid = contactManager.getDroid();

            // Get sending group
            Contacter group = accessor.getContacter(accessor.getDroid(droid.getNickName()), sender.getNickName());
            if (group == null) {
                logger.error("#handleMessageInternal - Get null contacter for droid [{}] and contacter nick name [{}]", droid.getNickName(), sender.getNickName());
                return;
            }

            // Get sending group member
            Contact fromUser = ripeMessage.getFromUser();
            if (fromUser == null) {
                logger.error("#handleMessageInternal - Get null from user, there must be somthing wrong...");
                return;
            }
            GroupMember groupMember = contactService.getGroupMember(group, fromUser.getNickName());
            if (groupMember == null) {
                logger.error("#handleMessageInternal - Can not find group member [{}] in group [{}]", fromUser.getNickName(), group.getId());
                return;
            }

            values.forEach(value -> {
                // Construct group mention log
                GroupMentionLog groupMentionLog = new GroupMentionLog(group, groupMember, ripeMessage.getData(), ripeMessage.getCreateTime());

                Optional<Command> command = commandService.getCommand(group, value);
                if (!command.isPresent()) {
                    logger.warn("#handleMessageInternal - No command found for keyword [{}], try to find default command", value);
                    command = commandService.getCommand(group, Constant.DEFAULT_COMMAND);
                }

                if (command.isPresent()) {
                    groupMentionLog.command(command.get());
                    executeCommand(command.get(), ripeMessage);
                } else {
                    logger.warn("#handleMessageInternal - No command found for keyword [{}] or default command [{}]", value, Constant.DEFAULT_COMMAND);
                }

                groupMentionLogService.create(groupMentionLog);
            });
        }
    }

    /**
     * @param command
     * @param ripeMessage
     */
    private void executeCommand(Command command, RipeMessage ripeMessage) {
        String template = command.getTemplate();
        TemplateType templateType = command.getTemplateTypeEnum();
        logger.trace("#executeCommand - Get command template [{}] of type [{}]", template, templateType);

        Optional<CommandOperator> operator = getOperator(templateType);
        if (operator.isPresent()) {
            operator.get().operate(command, ripeMessage);
        } else {
            logger.error("#executeCommand - No proper command operator found for template type [{}]", templateType);
        }
    }

    /**
     * @param templateType
     * @return
     */
    private Optional<CommandOperator> getOperator(TemplateType templateType) {
        CommandOperator operator = operatorCache.get(templateType);
        if (operator == null) {
            for (CommandOperator commandOperator : operators) {
                if (commandOperator.supportsCommand(templateType)) {
                    operatorCache.put(templateType, commandOperator);
                    return Optional.of(commandOperator);
                }
            }
            return Optional.empty();
        }
        return Optional.of(operator);
    }

    /**
     * @param ripeMessage
     * @return
     */
    private Optional<List<String>> extractMentionValues(RipeMessage ripeMessage) {
        if (MessageType.TEXT.equals(ripeMessage.getMessageType())) {
            String senderUserName = ripeMessage.getSender().getUserName();

            // the sender of `@` mention must be a group contact
            if (senderUserName.startsWith("@@")) {

                Map<String, List<String>> mentionMappings = new HashMap<>();

                Matcher matcher = MENTION_PATTERN.matcher(ripeMessage.getData());
                while (matcher.find()) {
                    String name = matcher.group(1);
                    List<String> values = Arrays.asList(matcher.group(2).split("\\h"));
                    if (mentionMappings.containsKey(name)) {
                        mentionMappings.get(name).addAll(values);
                    } else {
                        mentionMappings.put(name, values);
                    }
                }

                logger.trace("#extractMentionValues - Get mention mappings {}", mentionMappings);

                String droidName = contactManager.getDroid().getNickName();
                return Optional.ofNullable(mentionMappings.get(droidName));
            }
        }
        return Optional.empty();
    }

}
